use util::LruCacheItem;

use super::*;
use super::container::*;
use crate::{chunk::Chunk, constants::DefaultFingerprintType,rechunk::ReChunk};
use crate::recipe::ChunkPointer;
use super::ContainerId;

use std::collections::HashMap;
use std::ops::{Deref};
use std::sync::Arc;
use std::io::{Read,Seek,SeekFrom};
use std::fs::File;

pub struct ChunkContainerWriter {
    writer:ContainerWriter<Chunk>,
    fp_table:HashMap<DefaultFingerprintType,usize>,
}

impl ChunkContainerWriter {
    pub fn new(id:ContainerId) -> Self {
        Self {
            writer:ContainerWriter::new(id),
            fp_table:HashMap::new(),
        }
    }

    pub fn half(id:ContainerId,info:HalfContainerInfo) -> Self {
        Self {
            writer:ContainerWriter::half(id,info),
            fp_table:HashMap::new(),
        }
    }

	pub fn items(&self) -> &Vec<Arc<Chunk>> {
		self.writer.items()
	}

    pub fn append_item<P:ChunkPointer>(&mut self,item:Arc<Chunk>) -> ContainerResult<P> {
        let new_chunk:Arc<Chunk> = item.clone();
        let no = self.fp_table.get(new_chunk.fp());
        match no {
            Some(no) => {
                new_chunk.set_id_no(self.id(),(*no).try_into().unwrap());
                Ok(P::new(self.id(), (*no).try_into().unwrap(), new_chunk.fp()))
            }
            None => {
                let no = self.writer.append_item(item.clone())?;
                self.fp_table.insert(*item.fp(), no);
                new_chunk.set_id_no(self.id(),no.try_into().unwrap());
                Ok(P::new(self.id(), no.try_into().unwrap(), new_chunk.fp()))
            }
        }
    }

    pub fn find(&self,fp:&DefaultFingerprintType) -> Option<Arc<Chunk>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(self.get(no as u32))
        } else {
            None
        }
    }

    pub fn find_chunk_pointer<P:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> Option<Box<P>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(Box::new(P::new(self.id(), no.try_into().unwrap(), fp)))
        } else {
            None
        }
    }
}

impl Deref for ChunkContainerWriter {
    type Target = ContainerWriter<Chunk>;
    fn deref(&self) -> &Self::Target {
        &self.writer
    }
}

pub struct ChunkContainerReader {
    reader:ContainerReader<ReChunk>,
    fp_table:HashMap<DefaultFingerprintType,usize>,
}

impl ChunkContainerReader {
    pub fn new(id:ContainerId) -> Self {
        Self {
            reader:ContainerReader::new(id),
            fp_table:HashMap::new(),
        }
    }

	pub fn items(&self) -> &Vec<Arc<ReChunk>> {
		self.reader.items()
	}

    pub fn find(&self,fp:&DefaultFingerprintType) -> Option<Arc<ReChunk>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(self.reader.get(no as u32))
        } else {
            None
        }
    }

    pub fn find_chunk_pointer<P:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> Option<Box<P>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(Box::new(P::new(self.id(), no.try_into().unwrap(), fp)))
        } else {
            None
        }
    }

    pub fn on_read<F:Seek+Read>(&mut self,file:&mut F) -> ContainerResult<usize> {
        let byte_num = self.reader.on_read(file)?;
        for (i,c) in self.reader.get_all().iter().enumerate() {
            self.fp_table.insert(*c.fp(), i as usize);
        }
        Ok(byte_num)
    }
}

impl Deref for ChunkContainerReader {
    type Target = ContainerReader<ReChunk>;
    fn deref(&self) -> &Self::Target {
        &self.reader
    }
}

impl LruCacheItem for ChunkContainerReader {
	type ID = ContainerId;
	type SRC = File;
    type Error = ContainerError;
	fn id(&self) -> Self::ID {
		(self as &ContainerReader<ReChunk>).id()
	}
	
	fn miss_read(src:&mut Self::SRC,id:Self::ID) -> Result<Arc<Self>,Self::Error> {
		let mut con:Arc<ChunkContainerReader> = Arc::new(ChunkContainerReader::new(id));
		src.seek(SeekFrom::Start(container_position(id))).map_err(seek_err_from)?;
		Arc::get_mut(&mut con).unwrap().on_read(src)?;
		Ok(con)
	}
}